// Module representing the robot Karel itself

// imports the City module
import City = require("./City");

// Karel's orientation in the city
export enum eKarelDirection { North, South, East, West };

// Robot Karel class
export class RobotKarel {

    // maximum number of marks
    private mMaxMarks: number;

    // Karel's position in the city
    private mRow: number;
    private mCol: number;

    // Karel's direction
    private mDirection: eKarelDirection;

    // City reference
    private mCity: City.City;

    // instance attribute
    private static mInstance: RobotKarel = null;

    // constructor
    constructor() {
        this.mMaxMarks = 8;
        this.mCity = City.City.Instance;

        this.MoveHome();
    }

    // returns the maximum number of marks
    public get MaxMarks() {
        return this.mMaxMarks;
    }

    // returns the robot's direction
    public get Direction(): eKarelDirection {
        return this.mDirection;
    }

    // Karel's row
    public get Row() {
        return this.mRow;
    }

    // Karel's column
    public get Col() {
        return this.mCol;
    }

    // static singleton instance property getter
    public static get Instance() {
        if (RobotKarel.mInstance == null)
            RobotKarel.mInstance = new RobotKarel();
        return RobotKarel.mInstance;
    }

    // sets position of the robot karel in the city
    public SetPosition(row: number, col: number): void {
        this.mRow = row;
        this.mCol = col;
    }

    // changes the maximum number of marks
    public SetMaximumNumberOfMarks(maxMarks: number): void {
        this.mMaxMarks = maxMarks;
        // TO-DO: check the city - decrease maximum number of marks
    }

    // moves Karel home
    public MoveHome(): void {      
        // if at home wall, return
        if (this.mCity.IsWall(1, 1) == true)
            return;

        // move home
        this.mRow = 1;
        this.mCol = 1;
        this.mDirection = eKarelDirection.East;
    }

    // Moves the robot in the direction
    public Move(): void {
        if (this.CanMove() == false)
            throw "Cannot move Karel.";

        var result = this.GetFieldInDirection();
        this.mRow = result[0];
        this.mCol = result[1];       
    }

    // checks whether Karel can move
    public CanMove(): boolean {
        var result = this.GetFieldInDirection();
        return this.mCity.IsWall(result[0], result[1]) == false;
    }

    // Turns Karel left
    public TurnLeft(): void {
        switch (this.mDirection) {
            case eKarelDirection.North:
                this.mDirection = eKarelDirection.West;
                break;
            case eKarelDirection.South:
                this.mDirection = eKarelDirection.East;
                break;
            case eKarelDirection.West:
                this.mDirection = eKarelDirection.South;
                break;
            case eKarelDirection.East:
                this.mDirection = eKarelDirection.North;
                break;
        }
    }

    // checks whether a mark can be put
    public CanPutMark(): boolean {
        return this.mCity.GetNumberOfMarks(this.mRow, this.mCol) < this.mMaxMarks;
    }

    // checks whether we can pick mark
    public CanPickMark(): boolean {
        return this.mCity.GetNumberOfMarks(this.mRow, this.mCol) > 0;
    }

    // puts the mark
    public PutMark(): void {
        if (this.CanPutMark()) {
            this.mCity.PutMark(this.mRow, this.mCol);
        }
    }

    // picks the mark
    public PickMark(): void {
        if (this.CanPickMark()) {
            this.mCity.PickMark(this.mRow, this.mCol);
        }
    }

    // Export the Karel's and city's configuration
    public Export(): string {
        // get the city export
        var cityExport = this.mCity.Export();

        // save Karel's configuration
        var items = new Array<string>();
        items.push(this.mRow.toString());
        items.push(this.mCol.toString());

        // save orientation
        switch (this.mDirection) {
            case eKarelDirection.North:
                items.push("N");
                break;
            case eKarelDirection.South:
                items.push("S");
                break;
            case eKarelDirection.East:
                items.push("E");
                break;
            case eKarelDirection.West:
                items.push("W");
                break;
        }

        // create karel export
        var karelExport = items.join(",");

        // create the final export string
        return karelExport + ";" + cityExport;
    }

    // Import the city and Karel configuration
    public Import(configuration: string) {
        try {
            // divide city a Karel configuration
            var parts = configuration.split(";");

            // import the city configuration
            this.mCity.Import(parts[1]);

            // split Karel's items
            var items = parts[0].split(",");
            if (items.length != 3)
                return;

            // parse coordinates
            var n = parseInt(items[0]);
            if (isNaN(n) == false)
                this.mRow = n;

            n = parseInt(items[1]);
            if (isNaN(n) == false)
                this.mCol = n;

            // set orientation
            switch (items[2].toUpperCase()) {
                case "N":
                    this.mDirection = eKarelDirection.North;
                    break;
                case "S":
                    this.mDirection = eKarelDirection.South;
                    break;
                case "W":
                    this.mDirection = eKarelDirection.West;
                    break;
                case "E":
                    this.mDirection = eKarelDirection.East;
                    break;
            }
        }
        catch (e) {
        }
    }

    // Method saves configuration of the city to the local storage
    public Save(): void {
        var config = this.Export();
        window.localStorage["City"] = config;
    }

    // Method loads the city configuration from the local storage
    public Load(): void {
        // get the configuration
        var config = window.localStorage["City"];

        // if exists, import it
        if (config != undefined && config != null) {
            this.Import(config);
        }
    }

    // returns the field in given direction
    private GetFieldInDirection(): Array<number> {
        // get current position
        var row = this.mRow;
        var col = this.mCol;

        // calculate the next field
        switch (this.mDirection) {
            case eKarelDirection.North:
                row++;
                break;
            case eKarelDirection.South:
                row--;
                break;
            case eKarelDirection.East:
                col++;
                break;
            case eKarelDirection.West:
                col--;
                break;
            default:
                throw "Unknown direction."
        }

        // returns the result
        var result = new Array<number>();
        result.push(row);
        result.push(col);
        return result;
    }
}
